/*
 * FeedRepresentation.java
 *
 * Created on March 27, 2007, 11:55 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
import java.util.logging.Level;
import org.atomojo.app.auth.User;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.db.Feed;
import org.atomojo.app.util.Copy;
import org.atomojo.app.util.CopyFeedTask;
import org.atomojo.app.webdav.DAVCollection;
import org.atomojo.app.webdav.DAVResource;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Application;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.data.Tag;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;

/**
 *
 * @author alex
 */
public class FeedResource extends AtomResource {
   
   public static final String FEED_DOCUMENT_NAME = ".feed.atom";

   /*
   static class EntryDAVResource implements DAVResource {
      String ref;
      Entry entry;
      public EntryDAVResource(String feedRef,Entry entry)
      {
         this.ref = feedRef+"_entry_."+entry.getUUID()+".atom";
         this.entry = entry;
      }
                  
      public String getReference() {
         return ref;
      }
      public String getContentType() {
         return "application/atom+xml";
      }
      public Date getLastModified() {
         return entry.getEdited();
      }
      public Date getCreated() {
         return entry.getCreated();
      }
      public long getContentLength() {
         return -1;
      }
      public String getDisplayName() {
         return ref;
      }
   }
    *
    */
   /*
   static class MediaDAVResource implements DAVResource {
      String ref;
      EntryMedia media;
      public MediaDAVResource(String feedRef,EntryMedia media)
      {
         this.ref = feedRef+media.getName();
         this.media = media;
      }
                  
      public String getReference() {
         return ref;
      }
      public String getContentType() {
         return media.getMediaType().getName();
      }
      public Date getLastModified() {
         return media.getEdited();
      }
      public Date getCreated() {
         return media.getCreated();
      }
      public long getContentLength() {
         return -1;
      }
      public String getDisplayName() {
         return media.getName();
      }
   }
    * 
    */
   /*
   class FeedDAVCollection implements DAVCollection {
      String ref;
      Feed davFeed;
      public FeedDAVCollection(String ref,Feed davFeed)
      {
         this.ref = ref;
         this.davFeed = davFeed;
      }
                  
      public String getReference() {
         return ref;
      }
      public String getContentType() {
         return null;
      }
      public Date getLastModified() {
         return davFeed.getEdited();
      }
      public Date getCreated() {
         return davFeed.getCreated();
      }
      public long getContentLength() {
         return -1;
      }
      public String getDisplayName() {
         return davFeed.getName();
      }
      public Iterator<DAVResource> getChildren() {
         try {
            return new Iterator<DAVResource>() {
               Iterator<Feed> children = davFeed.getChildren();
               Iterator<Entry> entries = davFeed.getEntries();
               Iterator<EntryMedia> resources = null;

               Entry lastEntry = null;
               public boolean hasNext() {
                  return children.hasNext() || entries.hasNext() || (resources!=null && resources.hasNext());
               }
               public DAVResource next() {
                  if (resources!=null) {
                     if (resources.hasNext()) {
                        EntryMedia media = resources.next();
                        return new MediaDAVResource(ref,media);
                     } else {
                        resources = null;
                     }
                  }
                  if (entries.hasNext()) {
                     Entry entry = entries.next();
                     try {
                        resources = entry.getResources();
                        return new EntryDAVResource(ref,entry);
                     } catch (SQLException ex) {
                        throw new RuntimeException("Cannot get entry resources for entry "+entry.getUUID()+" due to: "+ex.getMessage());
                     }
                  } else if (children.hasNext()) {
                     Feed childFeed = children.next();
                     String childRef = ref+childFeed.getName();
                     return new FeedDAVCollection(childRef,childFeed);
                  } else {
                     return null;
                  }
               }
               public void remove() {
                  throw new UnsupportedOperationException("Cannot remove from interator.");
               }
            };
         } catch (SQLException ex) {
            getLogger().log(Level.SEVERE,"Cannot get children of feed.",ex);
            return null;
         }
      }
   }
    *
    */
   Reference myself;
   Storage store;
   App app;
   
   /** Creates a new instance of FeedRepresentation */
   public FeedResource(Application theApp,Feed feed,Reference myself,Storage storage) {
      super(theApp,feed,storage);
      this.myself = myself;
      this.app = new App(theApp.getContext().getLogger(),feed.getDB(),storage,theApp.getMetadataService());
   }

   public Representation get() {
      Form parameters = getRequest().getResourceRef().getQueryAsForm();
      try {
         /*
         if (getRequest().getConditions().getModifiedSince()!=null) { 
            getLogger().info("If-Modified-Since: "+AtomResource.toXSDDate(getRequest().getConditions().getModifiedSince()));
         }
         if (getRequest().getConditions().getUnmodifiedSince()!=null) {
            getLogger().info("If-Unodified-Since: "+AtomResource.toXSDDate(getRequest().getConditions().getUnmodifiedSince()));
         }
         getLogger().info("Modified: "+AtomResource.toXSDDate(feed.getEdited()));
         */
         String startS = parameters.getFirstValue("start");
         String maxS = parameters.getFirstValue("max");
         int start = startS==null ? 1 : Integer.parseInt(startS);
         int max = maxS==null ? -1 : Integer.parseInt(maxS);
         Representation rep = storage.getFeed(feed.getPath(),feed.getUUID(),feed.getEntries(start, max));
         if (rep!=null) {
            rep.setTag(new Tag(Long.toString(feed.getEdited().getTime()),false));
            // Don't trust the storage to get the modification right
            rep.setModificationDate(feed.getEdited());
            rep.setExpirationDate(feed.getEdited());
         }
         getResponse().setStatus(Status.SUCCESS_OK);
         return rep;
      } catch (IOException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot get feed due to I/O exception.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("I/O exception while processing feed.");
      } catch (SQLException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot get feed due to SQL exception.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Database exception while processing feed.");
      }
   }
   
   public String getTitle() 
      throws IOException
   {
      try {
         return storage.getFeedTitle(feed.getPath(),feed.getUUID());
      } catch (SQLException ex) {
         throw new IOException("Exception while getting feed path: "+ex.getMessage());
      }
   }
   
   public Representation post(Representation entity) {
      MediaType postType = entity.getMediaType();
      if (postType.getName().equals(MediaType.APPLICATION_ATOM.getName())) {
         String charset = postType.getParameters().getValues("charset");
         if (charset==null) {
            charset = "UTF-8";
         }
         // Should be an entry
         Document doc = null;
         if (entity instanceof InfosetRepresentation) {
            doc = ((InfosetRepresentation)entity).getDocument();
         } else {
            DocumentLoader loader = new SAXDocumentLoader();
            try {
               Reader r = new InputStreamReader(entity.getStream(),charset);
               doc = loader.load(r);
               r.close();
            } catch (IOException ex) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("I/O error while parsing document: "+ex.getMessage());
            } catch (XMLException ex) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("XML error while parsing document: "+ex.getMessage());
            }
         }
         
         User user = (User)getRequest().getAttributes().get(App.USER_ATTR);
         try {
            Entry entry = app.createEntry(user,feed,doc);
            
            getResponse().setStatus(Status.SUCCESS_CREATED);
            Form headers = new Form();
            getResponse().getAttributes().put("org.restlet.http.headers",headers);
            Reference ref = new Reference(myself+"_/"+entry.getUUID().toString());
            getResponse().setLocationRef(ref);
            Representation rep = new InfosetRepresentation(MediaType.APPLICATION_ATOM,doc);
            rep.setCharacterSet(CharacterSet.UTF_8);
            rep.setTag(new Tag(Long.toString(entry.getEdited().getTime()),false));
            MediaType entryType = new MediaType(rep.getMediaType().getName(),rep.getMediaType().getParameters().createSeries(EntryResource.entryParameters));
            rep.setMediaType(entryType);
            return rep;
            
         } catch (AppException ex) {
            getContext().getLogger().log(Level.SEVERE,ex.getMessage(),ex.getCause());
            getResponse().setStatus(ex.getStatus());
            return new StringRepresentation(ex.getMessage());
         }
         
      } else {
         // Get the slug for the resource & entry
         Form headers = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
         String slug = headers.getValues("slug");
         if (slug==null) {
            slug = headers.getValues("Slug");
         }
         if (slug!=null) {
            try {
               // Decode the precent encoding of the UTF-8 values
               slug = URLDecoder.decode(slug,"UTF-8");
               // Encode the slug as a URL encoding
               slug = URLEncoder.encode(slug,"UTF-8");
            } catch (UnsupportedEncodingException ex) {
               getLogger().log(Level.SEVERE,"Cannot decode slug value: "+slug,ex);
            }
            getContext().getLogger().info("Slug="+slug);
         }
         String idS = headers.getValues("id");
         UUID id = null;
         if (idS!=null) {
            try {
               id = UUID.fromString(idS);
            } catch (Exception ex) {
            }
         } else {
            id = UUID.randomUUID();
         }
         
         try {

            // Get author name for identity
            User user = (User)getRequest().getAttributes().get(App.USER_ATTR);
            Entry entry = app.createMediaEntry(user,feed,entity,slug,id);
            
            getResponse().setStatus(Status.SUCCESS_CREATED);
            headers = new Form();
            getResponse().getAttributes().put("org.restlet.http.headers",headers);
            Reference ref = new Reference(myself+"_/"+entry.getUUID().toString());
            getResponse().setLocationRef(ref);
            Representation rep = app.getEntryRepresentation(myself.toString(),entry);
            rep.setCharacterSet(CharacterSet.UTF_8);
            rep.setTag(new Tag(Long.toString(entry.getEdited().getTime()),false));
            return rep;
         } catch (AppException ex) {
            if (ex.getStatus().getCode()==Status.SERVER_ERROR_INTERNAL.getCode()) {
               getContext().getLogger().log(Level.SEVERE,ex.getMessage(),ex.getCause());
            }
            getResponse().setStatus(ex.getStatus());
            return new StringRepresentation(ex.getMessage());
         }
         
      }
   }
   public Representation put(Representation entity) {
      if (!entity.getMediaType().getName().equals(MediaType.APPLICATION_ATOM.getName())) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Cannot put to an feed using media type "+entity.getMediaType());
      }
      String charset = entity.getMediaType().getParameters().getValues("charset");
      if (charset==null) {
         charset = "UTF-8";
      }
      try {
         DocumentLoader loader = new SAXDocumentLoader();
         Reader r = new InputStreamReader(entity.getStream(),charset);
         Document doc = loader.load(r);
         r.close();
         
         try {
            app.updateFeed(feed,doc);
            getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
            return null;
         } catch (AppException ex) {
            if (ex.getStatus()==Status.SERVER_ERROR_INTERNAL) {
               getContext().getLogger().log(Level.SEVERE,ex.getMessage(),ex.getCause());
            }
            getResponse().setStatus(ex.getStatus());
            return new StringRepresentation(ex.getMessage());
         }
         
      } catch (XMLException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Cannot parse content: "+ex.getMessage());
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Fatal exception during post:"+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Fatal error "+ex.getMessage());
      }
      
   }
   
   
   public Representation delete() {
      
      try {
         app.delete(feed);
         getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
         return null;
      } catch (AppException ex) {
         if (ex.getStatus()==Status.SERVER_ERROR_INTERNAL) {
            getContext().getLogger().log(Level.SEVERE,ex.getMessage(),ex.getCause());
         }
         getResponse().setStatus(ex.getStatus());
         return new StringRepresentation(ex.getMessage());
      }
   }
   
   public Representation head() {
      getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
      Representation rep = new StringRepresentation("",MediaType.APPLICATION_ATOM);
      rep.setModificationDate(feed.getEdited());
      rep.setTag(new Tag(Long.toString(feed.getEdited().getTime()),false));
      return rep;
      /*
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
       */
   }

   @Copy
   public Representation copy() {
      
      Form requestHeaders = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
      String dest = requestHeaders==null ? null : requestHeaders.getFirstValue("Destination");
      if (dest==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return null;
      } else {
         Reference ref = getRequest().getRootRef().clone().addSegment("R/");
         User user = (User)getRequest().getAttributes().get(App.USER_ATTR);
         URI root = URI.create(ref.toString());
         String path = null;
         try {
            path = root.relativize(new URI(dest)).toString();
            String [] segments = path.split("/");
            Feed targetParent = feed.getDB().findFeedByPath(segments, 0, segments.length-1);
            if (targetParent==null) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("Cannot find parent of destination "+dest);
            }
            getLogger().info("Copy from "+feed.getPath()+" to "+targetParent.getPath()+"/"+segments[segments.length-1]);
            CopyFeedTask task = new CopyFeedTask(app,user,feed,targetParent,segments[segments.length-1]);
            task.run();
            if (task.getErrors().size()>0) {
               StringBuilder builder = new StringBuilder();
               for (String error : task.getErrors()) {
                  builder.append(error);
                  builder.append("\n");
               }
               getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
               return new StringRepresentation(builder.toString());
            } else {
               getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
               return null;
            }
            
         } catch (SQLException ex) {
            getLogger().log(Level.SEVERE,"Database error getting feed path "+path,ex);
            getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
            return new StringRepresentation("Database error.");
         } catch (URISyntaxException ex) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("Bad destination URI "+dest+" : "+ex.getMessage());
         }
      }
   }
   
   /*
   public void handleMove() {
      
      Form requestHeaders = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
      String dest = requestHeaders==null ? null : requestHeaders.getFirstValue("Destination");
      if (dest==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
      } else {
         Reference ref = getRequest().getRootRef().clone().addSegment("R/");
         URI root = URI.create(ref.toString());
         String path = null;
         try {
            path = root.relativize(new URI(dest)).toString();
            String [] segments = path.split("/");
            if (feed.getDB().findFeedByPath(segments)!=null) {
               getResponse().setStatus(Status.CLIENT_ERROR_CONFLICT);
               return;
            }
         } catch (SQLException ex) {
            getLogger().log(Level.SEVERE,"Database error getting feed path "+path,ex);
            getResponse().setStatus(Status.SERVER_ERROR_INTERNAL,"Database error.");
         } catch (URISyntaxException ex) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"Bad destination URI "+dest+" : "+ex.getMessage());
         }
      }
      getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
      Representation rep = new StringRepresentation("",MediaType.APPLICATION_ATOM_XML);
      rep.setModificationDate(feed.getEdited());
      rep.setTag(new Tag(Long.toString(feed.getEdited().getTime()),false));
      getResponse().setEntity(rep);
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
   }
   
   public void handleOptions() {
      super.handleOptions();
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
   }
   public void handlePropfind() {
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
      if (getRequest().isEntityAvailable()) {
         Form requestHeaders = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
         String value = requestHeaders==null ? null : requestHeaders.getFirstValue("Depth");
         final PropfindHandler propfind = new PropfindHandler(getLogger());
         if ("1".equals(value)) {
            getLogger().info("Request depth 1");
            propfind.setRequestDepth(1);
         } else if ("0".equals(value)) {
            getLogger().info("Request depth 0");
            propfind.setRequestDepth(0);
         } else if ("infinity".equals(value)) {
            getLogger().info("Request infinity");
            propfind.setRequestDepth(-1);
         }
         try {
            String xml = getRequest().getEntity().getText();
            getLogger().info(xml);
            propfind.loadRequest(new StringReader(xml));
         } catch (Exception ex) {
            getLogger().log(Level.SEVERE,"Cannot parse entity text.",ex);
         }
         getResponse().setStatus(Status.SUCCESS_MULTI_STATUS);
         getResponse().setEntity(new OutputRepresentation(MediaType.APPLICATION_XML) {
            public void write(OutputStream os) 
               throws IOException
            {
               Writer w = new OutputStreamWriter(os,"UTF-8");
               propfind.handle(w,new FeedDAVCollection(myself.toString(),feed));
               w.flush();
            }
         });
         getResponse().getEntity().setCharacterSet(CharacterSet.UTF_8);
      } else {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
      }
   }
    */
}
